package app.server.project.main.service.service.auth.tg;

import app.server.project.core.constants.Def;
import app.server.project.core.exception.SysError;
import app.server.project.core.token.TokenCache;
import app.server.project.core.util.AssertUtil;
import app.server.project.core.util.GlobalValChangeEvent;
import app.server.project.main.service.bo.event.AccountIPEvent;
import app.server.project.main.service.bo.event.ShareEvent;
import app.server.project.main.service.constants.GlobalCfgKey;
import app.server.project.main.service.db.dao.cfg.GlobalCfgDao;
import app.server.project.main.service.db.dao.user.AccountDao;
import app.server.project.main.service.db.dao.user.UserExtLock;
import app.server.project.main.service.db.dao.user.UserInfoDao;
import app.server.project.main.service.db.entity.user.AccountEntity;
import app.server.project.main.service.db.entity.user.UserExtEntity;
import app.server.project.main.service.db.entity.user.UserInfoEntity;
import app.server.project.main.service.dto.ADDto;
import app.server.project.main.service.dto.auth.TGLoginReq;
import app.server.project.main.service.dto.auth.TGLoginResp;
import app.server.project.main.service.service.LockService;
import app.server.project.main.service.service.auth.ChannelType;
import cn.hutool.cache.CacheUtil;
import cn.hutool.cache.impl.TimedCache;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArraySet;


@Service
@RequiredArgsConstructor
@Slf4j
public class TGAuthService {


    private final long CacheTime = 15 * 60 * 1000;

    private final AccountDao accountDao;
    private final GlobalCfgDao globalCfgDao;
    private final UserInfoDao userInfoDao;
    private final UserExtLock userExtLock;
    private final ApplicationEventPublisher applicationEventPublisher;

    private final LockService lockService;
    @Qualifier("CommonPool")
    private final TaskExecutor executor;

    @Getter
    private CopyOnWriteArraySet<Long> tgIdSet = new CopyOnWriteArraySet<>();
    private TimedCache<String, Long> cache = CacheUtil.newTimedCache(CacheTime);

    @EventListener(classes = {ApplicationReadyEvent.class, GlobalValChangeEvent.class})
    public void init() {
        TokenCache.setDebug(this.globalCfgDao.getDebug());
    }


    @Async(Def.CommonPool)
    @EventListener(classes = {ApplicationReadyEvent.class})
    public void initTGIdCache() {
        var dbs = this.accountDao.getBaseMapper().getAllOpenId();
        tgIdSet.addAll(dbs);
        log.info("成功加载广告商数据");
    }

    private void addTgId(String openId) {
        this.executor.execute(() -> {
            if (!NumberUtil.isLong(openId)) {
                return;
            }
            this.tgIdSet.add(NumberUtil.parseLong(openId));
        });
    }

    public ADDto chkTGUser(long openId) {
        return ADDto.of(this.tgIdSet.contains(openId), true);
    }


    private void chkIp(String ip, TGUser tgUser) {
        if (!this.cache.containsKey(ip)) return;
        var cacheValue = this.cache.get(ip);
        if (cacheValue == null) {
            return;
        }
        AssertUtil.isTrue(tgUser.getId() == cacheValue, SysError.MoreUser);
    }

    private void cacheIp(String ip, TGUser tgUser) {

        var cacheTime = CacheTime;
        if (this.globalCfgDao.has(GlobalCfgKey.Login_IP_Cache_Time)) {
            cacheTime = this.globalCfgDao.getOneByKey(GlobalCfgKey.Login_IP_Cache_Time).getLong();
        }
        this.cache.put(ip, tgUser.getId(), cacheTime);
    }

    public TGLoginResp tgLogin(TGLoginReq req, String ip) {
        boolean chk = true;
        //开始校验token
        AssertUtil.isTrue(StrUtil.isNotBlank(req.getOpenId()), SysError.EmptyStr);
        if (this.globalCfgDao.has(GlobalCfgKey.Server_Debug)) {
            chk = !(this.globalCfgDao.getOneByKey(GlobalCfgKey.Server_Debug).getBool());
        }
        if (chk) {
            AssertUtil.isTrue(StrUtil.isNotBlank(req.getInitData()), SysError.InitData);
            AssertUtil.isTrue(NumberUtil.isNumber(req.getOpenId()), SysError.TG_NUMBER);
            var tgUser = TelegramMiniAppVerifier.convertToTGUser(req.getInitData());
            AssertUtil.isTrue(Objects.nonNull(tgUser), SysError.InitData);

            //检查15分钟缓存
            this.chkIp(ip, tgUser);
            String botToken = this.globalCfgDao.getStr(GlobalCfgKey.BOT_TOKEN);
            var chkRet = TelegramMiniAppVerifier.verify(req.getInitData(), botToken);
            AssertUtil.isTrue(chkRet.isSuccess(), SysError.TG_Token_Error);
            AssertUtil.isTrue(tgUser.getId() == Long.parseLong(req.getOpenId()), SysError.TG_Token_Error);
            //存储ip对应用户id
            cacheIp(ip, tgUser);
        }

        synchronized (this.lockService.getLock(req.getOpenId())) {


            AssertUtil.isTrue(StrUtil.isNumeric(req.getOpenId()), SysError.OPEN_ID);

            AccountEntity accountEntity = this.accountDao.getDataByOpenId(req.getOpenId(), ChannelType.TG);
            AssertUtil.isTrue(!accountEntity.getLock(), SysError.LOCK);
            String token = TokenCache.addToCache(accountEntity.getUserId());
            UserInfoEntity userInfoEntity = this.userInfoDao.getUserInfoById(accountEntity.getUserId());
            userInfoEntity.setNick(req.getNick());
            userInfoEntity.setHeadUrl(req.getHeadUrl());
            UserExtEntity userExtEntity = this.userExtLock.getUserExtByUserExtId(userInfoEntity.getUserId());

            //开始检查分享逻辑
            if (req.getSharedId() > 0 && userExtEntity.getShareFlag() == Def.Zero) {
                this.applicationEventPublisher.publishEvent(ShareEvent.of(req.getSharedId(), userExtEntity.getId(), req.getIsPremium()));
            }
            if (userExtEntity.getShareFlag() == Def.Zero) {
                userExtEntity.setShareFlag(Def.True);
            }
            //开始检查广告渠道
            if (StrUtil.isNotBlank(req.getChannel()) && StrUtil.isBlank(accountEntity.getAdChannel())) {
                accountEntity.setAdChannel(req.getChannel());
            }
            this.addTgId(req.getOpenId());
            //发送ip事件
            this.applicationEventPublisher.publishEvent(AccountIPEvent.of(ip, accountEntity.getUserId(), LocalDateTime.now()));
            return TGLoginResp.of(String.valueOf(accountEntity.getUserId()), token, req.getOpenId());
        }
    }
}
